/*
 *
 * Copyright (c) 2025 Bocloud Technologies Co., Ltd.
 * installer is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain n copy of Mulan PSL v2 at:
 *          http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
 *
 */
package validation

import (
	"fmt"
	"testing"

	"github.com/stretchr/testify/assert"

	"gopkg.openfuyao.cn/bkecommon/cluster/addon"
	"gopkg.openfuyao.cn/bkecommon/cluster/api/v1beta1"
	"gopkg.openfuyao.cn/bkecommon/cluster/node"
)

func TestValidate(t *testing.T) {
	t.Run("ValidateBKEConfig", TestValidateBKEConfig)
	t.Run("ValidateNonStandardBKEConfig", TestValidateNonStandardConfig)
	t.Run("ValidateNodesFields", TestValidateNodesFields)
	t.Run("ValidateAddons", TestValidateAddons)
}

var (
	baseCfg = v1beta1.BKEConfig{
		Cluster: v1beta1.Cluster{
			ControlPlane: v1beta1.ControlPlane{
				ControllerManager: &v1beta1.ControlPlaneComponent{
					ExtraVolumes: nil,
				},
				Scheduler: &v1beta1.ControlPlaneComponent{
					ExtraVolumes: []v1beta1.HostPathMount{
						{
							Name:      "test",
							HostPath:  "/host/test",
							MountPath: "/host/test",
							ReadOnly:  true,
							PathType:  "Directory",
						},
					},
				},
				APIServer: &v1beta1.APIServer{

					APIEndpoint: v1beta1.APIEndpoint{
						Host: "",
						Port: 6443,
					},
					ControlPlaneComponent: v1beta1.ControlPlaneComponent{
						ExtraArgs:    nil,
						ExtraVolumes: nil,
					},
					CertSANs: nil,
				},
				Etcd: &v1beta1.Etcd{
					DataDir: "/var/lib/etcd",
					ControlPlaneComponent: v1beta1.ControlPlaneComponent{
						ExtraArgs:    map[string]string{},
						ExtraVolumes: nil,
					},
					ServerCertSANs: nil,
					PeerCertSANs:   nil,
				},
			},
			Kubelet: &v1beta1.Kubelet{
				ControlPlaneComponent: v1beta1.ControlPlaneComponent{
					ExtraArgs:    map[string]string{},
					ExtraVolumes: nil,
				},
				ManifestsDir: "/etc/kubernetes/manifests",
			},

			Networking: v1beta1.Networking{
				ServiceSubnet: "10.96.0.0/12",
				PodSubnet:     "192.168.0.0/16",
				DNSDomain:     "cluster.local",
			},
			KubernetesVersion: "v1.21.1",
			CertificatesDir:   "/etc/kubernetes/pki",
			NTPServer:         "cn.pool.ntp.org:123",
		},
		Nodes: []v1beta1.Node{
			v1beta1.Node{
				ControlPlane: v1beta1.ControlPlane{
					APIServer: &v1beta1.APIServer{
						APIEndpoint: v1beta1.APIEndpoint{
							Host: "172.100.200.10",
							Port: 6443,
						},
						ControlPlaneComponent: v1beta1.ControlPlaneComponent{
							ExtraArgs:    map[string]string{},
							ExtraVolumes: nil,
						},
						CertSANs: nil,
					},
				},
				IP:       "172.100.200.10",
				Port:     "22",
				Username: "root",
				Password: "password0",
				Hostname: "master-1",
				Role: []string{
					node.MasterNodeRole,
					node.EtcdNodeRole,
				},
			},
			v1beta1.Node{
				IP:       "172.100.200.11",
				Port:     "22",
				Username: "root",
				Password: "password1",
				Hostname: "master-2",
				Role: []string{
					node.MasterNodeRole,
					node.EtcdNodeRole,
				},
			},
			v1beta1.Node{
				IP:       "172.100.200.12",
				Port:     "22",
				Username: "root",
				Password: "password2",
				Hostname: "master-3",
				Role: []string{
					node.MasterNodeRole,
					node.EtcdNodeRole,
				},
			},
			v1beta1.Node{
				IP:       "172.100.200.20",
				Port:     "22",
				Username: "root",
				Password: "password2",
				Hostname: "master-4",
				Role: []string{
					node.MasterWorkerNodeRole,
					node.EtcdNodeRole,
				},
			},
			v1beta1.Node{
				IP:       "172.100.200.21",
				Port:     "22",
				Username: "root",
				Password: "password2",
				Hostname: "master-5",
				Role: []string{
					node.MasterWorkerNodeRole,
					node.EtcdNodeRole,
				},
			},
			v1beta1.Node{
				IP:       "172.100.200.13",
				Port:     "22",
				Username: "root",
				Password: "password3",
				Hostname: "worker-1",
				Role: []string{
					node.WorkerNodeRole,
				},
			},
			v1beta1.Node{
				IP:       "172.100.200.14",
				Port:     "22",
				Username: "root",
				Password: "password4",
				Hostname: "worker-2",
				Role: []string{
					node.WorkerNodeRole,
				},
			},
			v1beta1.Node{
				IP:       "172.100.200.15",
				Port:     "22",
				Username: "root",
				Password: "password5",
				Hostname: "worker-3",
				Role: []string{
					node.WorkerNodeRole,
				},
			},
		},
	}

	evenMasterCfg = v1beta1.BKEConfig{}

	invalidDNSDomainCfg = v1beta1.BKEConfig{}

	invalidSubnetCfg = v1beta1.BKEConfig{}

	nonStandardCfg = v1beta1.BKEConfig{
		Cluster: v1beta1.Cluster{
			ControlPlane: v1beta1.ControlPlane{
				APIServer:         nil,
				ControllerManager: nil,
				Scheduler:         nil,
				Etcd:              nil,
			},
			Kubelet: nil,
			Networking: v1beta1.Networking{
				ServiceSubnet: "192.168.1.1/24",
				PodSubnet:     "123.1.1.1/24",
				DNSDomain:     "cluster.local",
			},
			KubernetesVersion: "",
			CertificatesDir:   "",
			NTPServer:         "",
			HTTPRepo: v1beta1.Repo{
				Domain: "bke.repo.com",
			},
			ImageRepo: v1beta1.Repo{
				Domain: "bke.repo.com",
			},
		},
		Nodes:  nil,
		Addons: nil,
	}
)

func init() {

	evenMasterCfg = baseCfg
	invalidDNSDomainCfg = baseCfg
	invalidSubnetCfg = baseCfg

	evenMasterCfg.Nodes = []v1beta1.Node{
		v1beta1.Node{
			IP:       "172.100.200.10",
			Port:     "22",
			Username: "root",
			Password: "password0",
			Hostname: "master-1",
			Role: []string{
				node.MasterNodeRole,
				node.EtcdNodeRole,
			},
		},
		v1beta1.Node{
			IP:       "172.100.200.10",
			Port:     "22",
			Username: "root",
			Password: "password0",
			Hostname: "master-1",
			Role: []string{
				node.MasterNodeRole,
				node.EtcdNodeRole,
			},
		},
	}

	invalidDNSDomainCfg.Cluster.Networking.DNSDomain = "_abc.A"

	invalidSubnetCfg.Cluster.Networking.PodSubnet = "127.0.0.1"
}

func TestValidateBKEConfig(t *testing.T) {
	t.Run("InvalidDNSDomainCfg", TestValidateInvalidDNSDomainCfg)
	t.Run("InvalidSubnetCfg", testValidateInvalidSubnetCfg)
	t.Run("validateRepo", testValidateRepo)
	//t.Run("validateImageRepo", TestValidateImageRepo)
	t.Run("validateK8sVersion", TestValidateK8sVersion)
	t.Run("validateContainerRuntime", TestValidateContainerRuntime)
	t.Run("validateDockerAvailable", TestValidateDockerAvailable)
}

func TestValidateNonStandardConfig(t *testing.T) {
	err := ValidateNonStandardBKEConfig(nonStandardCfg)
	assert.Equal(t, nil, err, "nonStandardCfg should be valid")
}

func TestValidateInvalidDNSDomainCfg(t *testing.T) {
	err := ValidateBKEConfig(invalidDNSDomainCfg)
	if assert.Error(t, err) {
		assert.Equal(t, err.Error(), fmt.Sprintf("%q is not valid domain, a lowercase RFC 1123 subdomain must "+
			"consist of lower case alphanumeric characters, '-' or '.', and must start and end with an alphanumeric "+
			"character (e.g. 'example.com')", "_abc.A"))
	}
}

func testValidateInvalidSubnetCfg(t *testing.T) {
	err := ValidateBKEConfig(invalidSubnetCfg)
	if assert.Error(t, err) {
		assert.Equal(t, err.Error(), fmt.Sprintf("Cluster podSubnet %q is invalid, couldn't parse subnet", "127.0.0.1"))
	}
}

func testValidateRepo(t *testing.T) {
	tests := []struct {
		name    string
		repo    v1beta1.Repo
		wantErr bool
	}{
		{
			name: "repo only ip port without domain",
			repo: v1beta1.Repo{
				Ip:   "127.0.0.1",
				Port: "40443",
			},
			wantErr: false,
		},
		{
			name: "repo ip port domain",
			repo: v1beta1.Repo{
				Domain: "harbor.test.com",
				Ip:     "127.0.0.1",
				Port:   "40443",
			},
			wantErr: false,
		},
		{
			name: "repo only domain",
			repo: v1beta1.Repo{
				Domain: "harbor.test.com",
			},
			wantErr: false,
		},
		{
			name: "repo only ip",
			repo: v1beta1.Repo{
				Ip: "127.0.0.1",
			},
			wantErr: false,
		},
		{
			name: "repo only port",
			repo: v1beta1.Repo{
				Port: "22",
			},
			wantErr: true,
		},
		{
			name: "invalidate repo domain",
			repo: v1beta1.Repo{
				Domain: "-aa-",
			},
			wantErr: true,
		},
		{
			name: "invalidate repo Ip",
			repo: v1beta1.Repo{
				Ip: "-aa-",
			},
			wantErr: true,
		},
	}

	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := ValidateRepo(tt.repo); (err != nil) != tt.wantErr {
				t.Errorf("ValidateRepo() error = %s, wantErr %v", err.Error(), tt.wantErr)
			}
		})
	}
}

func TestValidateImageRepo(t *testing.T) {
	tests := []struct {
		name  string
		repo  v1beta1.Repo
		valid bool
	}{
		{
			name: "valid public repo1",
			repo: v1beta1.Repo{
				Domain: "registry.cn-hangzhou.aliyuncs.com",
			},
			valid: true,
		},
		{
			name: "invalid public repo1",
			repo: v1beta1.Repo{
				Domain: "registry.cn-hangzhou.aliyuncs.com",
				Port:   "2222",
			},
			valid: false,
		},
		{
			name: "valid public repo2",
			repo: v1beta1.Repo{
				Domain: "ccr.ccs.tencentyun.com",
			},
			valid: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if address, ok := ValidImageRepo(tt.repo); ok != tt.valid {
				t.Errorf("test %s \n ValidImageRepo() address = %s, wantErr %v", tt.name, address, tt.valid)
			}
		})
	}
}

func TestValidateK8sVersion(t *testing.T) {
	tests := []struct {
		name    string
		version string
		wantErr bool
	}{
		{
			name:    "invalid k8s version",
			version: "v1.15.0",
			wantErr: true,
		},
		{
			name:    "invalid k8s version",
			version: "v1.19.0",
			wantErr: true,
		},
		{
			name:    "valid k8s version",
			version: "v1.21",
			wantErr: false,
		},
		{
			name:    "valid k8s version",
			version: "v1.25.0",
			wantErr: false,
		},
		{
			name:    "valid k8s version",
			version: "v1.21.0",
			wantErr: false,
		},
		{
			name:    "valid k8s version",
			version: "v1.25.6",
			wantErr: false,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := ValidateK8sVersion(tt.version); (err != nil) != tt.wantErr {
				t.Errorf("ValidateK8sVersion() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestValidateContainerRuntime(t *testing.T) {
	tests := []struct {
		name    string
		runtime v1beta1.ContainerRuntime
		wantErr bool
	}{
		{
			name: "valid container runtime",
			runtime: v1beta1.ContainerRuntime{
				CRI:     "docker",
				Runtime: "runc",
			},
			wantErr: false,
		},
		{
			name: "invalid container runtime",
			runtime: v1beta1.ContainerRuntime{
				CRI:     "docker",
				Runtime: "runc1",
			},
			wantErr: true,
		},
		{
			name: "invalid container runtime",
			runtime: v1beta1.ContainerRuntime{
				CRI:     "docker1",
				Runtime: "runc",
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := ValidateContainerRuntime(tt.runtime); (err != nil) != tt.wantErr {
				t.Errorf("ValidateContainerRuntime() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestValidateDockerAvailable(t *testing.T) {
	tests := []struct {
		name    string
		version string
		runtime v1beta1.ContainerRuntime
		wantErr bool
	}{
		{
			name:    "available v1.21",
			version: "v1.21.1",
			runtime: v1beta1.ContainerRuntime{CRI: "docker"},
			wantErr: false,
		},
		{
			name:    "available v1.22",
			version: "v1.22",
			runtime: v1beta1.ContainerRuntime{CRI: "docker"},
			wantErr: false,
		},
		{
			name:    "unavailable v1.24",
			version: "v1.24",
			runtime: v1beta1.ContainerRuntime{CRI: "docker"},
			wantErr: true,
		},
		{
			name:    "unavailable v1.27",
			version: "v1.27",
			runtime: v1beta1.ContainerRuntime{CRI: "docker"},
			wantErr: true,
		},
	}
	for _, test := range tests {
		t.Run(test.name, func(t *testing.T) {
			if err := ValidateDockerAvailable(test.version, test.runtime); err != nil {
				t.Errorf("ValidateDockerAvailable() error = %v, wantErr = %v", err, test.wantErr)
			}
		})

	}

}

var (
	nodesWorkerNodeWithControlplaneconfig = node.Nodes{
		v1beta1.Node{
			ControlPlane: v1beta1.ControlPlane{
				APIServer: &v1beta1.APIServer{
					APIEndpoint: v1beta1.APIEndpoint{
						Host: "114.114.114.114",
						Port: 6443,
					},
					ControlPlaneComponent: v1beta1.ControlPlaneComponent{
						ExtraArgs: map[string]string{
							"a": "n",
						},
						ExtraVolumes: nil,
					},
					CertSANs: nil,
				},
			},
			Kubelet:  nil,
			IP:       "123.123.123.123",
			Port:     "22",
			Username: "root",
			Password: "password",
			Hostname: "worker-1",
			Role: []string{
				node.WorkerNodeRole,
			},
		},
		v1beta1.Node{
			IP:       "127.0.0.1",
			Port:     "22",
			Username: "root",
			Password: "password",
			Hostname: "master-1",
			Role: []string{
				node.MasterNodeRole,
				node.EtcdNodeRole,
			},
		},
	}

	nodesLostFields = node.Nodes{
		v1beta1.Node{
			IP:       "",
			Port:     "22",
			Username: "root",
			Password: "password",
			Hostname: "worker-1",
			Role: []string{
				node.WorkerNodeRole,
			},
		},
		v1beta1.Node{
			IP:       "127.0.0.2",
			Port:     "22",
			Username: "root",
			Password: "password",
			Hostname: "master-1",
			Role: []string{
				node.MasterNodeRole,
				node.EtcdNodeRole,
			},
		},
	}

	nodesWorkerNodeWithEtcdRole = node.Nodes{
		v1beta1.Node{
			IP:       "127.0.0.1",
			Port:     "22",
			Username: "root",
			Password: "password",
			Hostname: "worker-1",
			Role: []string{
				node.WorkerNodeRole,
				node.EtcdNodeRole,
			},
		},
		v1beta1.Node{
			IP:       "127.0.0.2",
			Port:     "22",
			Username: "root",
			Password: "password",
			Hostname: "master-1",
			Role: []string{
				node.MasterNodeRole,
				node.EtcdNodeRole,
			},
		},
	}

	nodesMasterNodeInvalidIP = node.Nodes{
		v1beta1.Node{
			IP:       "abc",
			Port:     "22",
			Username: "root",
			Password: "password",
			Hostname: "master-1",
			Role: []string{
				node.MasterWorkerNodeRole,
				node.EtcdNodeRole,
			},
		},
	}

	nodesMasterNodeInvalidStringLen = node.Nodes{
		v1beta1.Node{
			IP:       "127.0.0.1",
			Port:     "22",
			Username: "root",
			Password: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
			Hostname: "master-1",
			Role: []string{
				node.MasterWorkerNodeRole,
				node.EtcdNodeRole,
			},
		},
	}

	nodesHaveSameHostname = node.Nodes{
		v1beta1.Node{
			IP:       "127.0.0.1",
			Port:     "22",
			Username: "root",
			Password: "password",
			Hostname: "master-1",
			Role: []string{
				node.MasterWorkerNodeRole,
				node.EtcdNodeRole,
			},
		},
		v1beta1.Node{
			IP:       "127.0.0.2",
			Port:     "22",
			Username: "root",
			Password: "password",
			Hostname: "master-1",
			Role: []string{
				node.MasterNodeRole,
				node.EtcdNodeRole,
			},
		},
		v1beta1.Node{
			IP:       "127.0.0.3",
			Port:     "22",
			Username: "root",
			Password: "password",
			Hostname: "master-2",
			Role: []string{
				node.MasterNodeRole,
				node.EtcdNodeRole,
			},
		},
	}

	nodesHaveSameIP = node.Nodes{
		v1beta1.Node{
			IP:       "127.0.0.1",
			Port:     "22",
			Username: "root",
			Password: "password",
			Hostname: "master-1",
			Role: []string{
				node.MasterNodeRole,
				node.EtcdNodeRole,
			},
		},
		v1beta1.Node{
			IP:       "127.0.0.1",
			Port:     "22",
			Username: "root",
			Password: "password",
			Hostname: "master-2",
			Role: []string{
				node.WorkerNodeRole,
			},
		},
	}
	noNodes = node.Nodes{}
	noEtcd  = node.Nodes{
		v1beta1.Node{
			IP:       "abc",
			Port:     "22",
			Username: "root",
			Password: "password",
			Hostname: "master-1",
			Role: []string{
				node.MasterWorkerNodeRole,
			},
		},
	}
	noWorker = node.Nodes{
		v1beta1.Node{
			IP:       "1.1.1.1",
			Port:     "22",
			Password: "password",
			Hostname: "master-1",
			Role: []string{
				node.MasterNodeRole,
				node.EtcdNodeRole,
			},
		},
	}

	noHostname = node.Nodes{
		v1beta1.Node{
			IP:       "1.1.1.1",
			Port:     "22",
			Username: "root",
			Password: "password",
			Hostname: "",
			Role: []string{
				node.MasterWorkerNodeRole,
				node.EtcdNodeRole,
			},
		},
	}
	nonStandardNodes = node.Nodes{
		v1beta1.Node{
			IP:       "1.1.1.1",
			Port:     "",
			Username: "",
			Password: "",
			Hostname: "master-1",
			Role: []string{
				node.MasterWorkerNodeRole,
				node.EtcdNodeRole,
			},
		},
		v1beta1.Node{
			IP:       "2.2.2.2",
			Port:     "22",
			Username: "",
			Password: "",
			Hostname: "worker-2",
			Role: []string{
				node.WorkerNodeRole,
			},
		},
	}
)

func TestValidateNodesFields(t *testing.T) {
	t.Run("WorkerNodeWithControlplaneconfig", TestWorkerNodeWithControlplaneconfig)
	t.Run("LostField", TestLostField)
	t.Run("WorkerNodeWithEtcdRole", TestWorkerNodeWithEtcdRole)
	t.Run("MasterNodeInvalidIP", TestMasterNodeInvalidIP)
	t.Run("MasterNodeInvalidStringLen", TestMasterNodeInvalidStringLen)
	t.Run("MasterNodeCountEven", TestMasterNodeCountEven)
	t.Run("SameHostname", TestSameHostname)
	t.Run("SameIP", testSameIP)
	t.Run("NoNodes", TestNoNodes)
	t.Run("NoEtcd", TestNoEtcd)
	t.Run("NoWorker", TestNoWorker)
	t.Run("NodesNoHostName", TestNodesNoHostName)
	t.Run("nonStandardNodes", TestNonStandardNodes)
}

func TestWorkerNodeWithControlplaneconfig(t *testing.T) {
	err := ValidateNodesFields(nodesWorkerNodeWithControlplaneconfig)
	if assert.Error(t, err) {
		assert.Equal(t, err.Error(), "node worker-1/123.123.123.123 with \"node\" role cannot configure control plane components")
	}
}

func TestLostField(t *testing.T) {
	err := ValidateNodesFields(nodesLostFields)
	if assert.Error(t, err) {
		assert.Equal(t, err.Error(), "node worker-1 field \"IP\" is required")
	}
}

func TestWorkerNodeWithEtcdRole(t *testing.T) {
	err := ValidateNodesFields(nodesWorkerNodeWithEtcdRole)
	if assert.Error(t, err) {
		assert.Equal(t, err.Error(), "node worker-1/127.0.0.1 with \"node\" role cannot set \"node\" and \"etcd\" roles at the same time")
	}
}

func TestMasterNodeInvalidIP(t *testing.T) {
	err := ValidateNodesFields(nodesMasterNodeInvalidIP)
	if assert.Error(t, err) {
		assert.Equal(t, err.Error(), "node master-1/abc field IP \"abc\" is not valid")
	}

}

func TestMasterNodeInvalidStringLen(t *testing.T) {
	err := ValidateNodesFields(nodesMasterNodeInvalidStringLen)
	if assert.Error(t, err) {
		assert.Equal(t, err.Error(), "nodes master-1/127.0.0.1 field \"Password\" cannot be longer than 255 characters")
	}
}

func TestMasterNodeCountEven(t *testing.T) {
	err := ValidateNodesFields(evenMasterCfg.Nodes)
	if assert.Error(t, err) {
		assert.Equal(t, err.Error(), fmt.Sprintf("nodes %q and %q role node count must be odd", node.MasterNodeRole, node.MasterWorkerNodeRole))
	}
}

func TestSameHostname(t *testing.T) {
	err := ValidateNodesFields(nodesHaveSameHostname)
	if assert.Error(t, err) {
		assert.Equal(t, err.Error(), fmt.Sprintf("node field is not valid, hostname is not unique %q", "master-1"))
	}
}

func TestNoNodes(t *testing.T) {
	err := ValidateNodesFields(noNodes)
	if assert.Error(t, err) {
		assert.Equal(t, err.Error(), "nodes is required")
	}
}

func TestNoEtcd(t *testing.T) {
	err := ValidateNodesFields(noEtcd)
	if assert.Error(t, err) {
		assert.Equal(t, err, NoEtcdNodeError)
	}
}

func TestNoWorker(t *testing.T) {
	err := ValidateNodesFields(noWorker)
	if assert.Error(t, err) {
		assert.Equal(t, err, NoWorkerNodeError)
	}
}

func TestNodesNoHostName(t *testing.T) {
	err := ValidateNodesFields(noHostname)
	assert.Equal(t, nil, err, "expected no error, got %v", err)
}

func TestNonStandardNodes(t *testing.T) {
	err := ValidateNonStandardNodesFields(nonStandardNodes)
	assert.Equal(t, nil, err, "expected no error, got %v", err)
}

func testSameIP(t *testing.T) {
	err := ValidateNodesFields(nodesHaveSameIP)
	if assert.Error(t, err) {
		assert.Equal(t, err.Error(), fmt.Sprintf("node field is not valid, IP is not unique %q", "127.0.0.1"))
	}
}

func TestValidateAddons(t *testing.T) {
	t.Run("Valid", testValidAddons)
	t.Run("Invalid", testInvalidAddons)
}

func testValidAddons(t *testing.T) {
	// todo
}

func testInvalidAddons(t *testing.T) {
	tests := []struct {
		name    string
		args    addon.Addons
		wantErr bool
	}{
		{
			name: "not unique addons",
			args: addon.Addons{
				{
					Name:    "kubeproxy",
					Version: "1.21.1",
				},
				{
					Name:    "kubeproxy",
					Version: "1.21.1",
				},
			},
			wantErr: true,
		},
	}
	for _, tt := range tests {
		t.Run(tt.name, func(t *testing.T) {
			if err := ValidateAddons(tt.args); (err != nil) != tt.wantErr {
				t.Errorf("ValidateAddons() error = %v, wantErr %v", err, tt.wantErr)
			}
		})
	}
}

func TestValidCustomExtra(t *testing.T) {
	err := ValidateCustomExtra(map[string]string{
		"containerd": "containerd-1.6.0-linux-{.arch}.tar.gz",
	})
	assert.Nil(t, err)
	err = ValidateCustomExtra(map[string]string{
		"containerd": "containerd-1.6.0-linux-amd64.tar.gz",
	})
	assert.Nil(t, err)

	err = ValidateCustomExtra(map[string]string{
		"containerd": "codntainerd-1.6.0-linux-amd64.tar.gz",
	})
	assert.NotNil(t, err)
}
